Skip to content

8822E: fix MCS4+/48M+ TX, resolve the 0x41e8 RX-desense quirk, port kernel channel/runtime obligations + quirks doc (#238)#268

Merged
josephnef merged 2 commits into
masterfrom
238-8822e-quirks-campaign
Jul 12, 2026
Merged

8822E: fix MCS4+/48M+ TX, resolve the 0x41e8 RX-desense quirk, port kernel channel/runtime obligations + quirks doc (#238)#268
josephnef merged 2 commits into
masterfrom
238-8822e-quirks-campaign

Conversation

@josephnef

Copy link
Copy Markdown
Collaborator

Closes the reproducible half of #238 on the reference hardware (LB-LINK BL-M8812EU2, rfe-21) and lands the issue's deliverable, docs/8822e-quirks.md. Everything below is hardware-validated on the two-adapter bench (8812EU ↔ 8822CU, plus the vendor kernel and an in-tree rtw88 ground as controls); reproducers ship under tests/.

Root causes & fixes

  • MCS4+/48M+ whole-PPDU TX corruption (the follow-up comments' bug): two interacting halves —
    1. the DPDT antenna switch was never routed to WL control (halmac pin-mux family devourer's hand-rolled MAC init skips): REG_LED_CFG 0x4c[24] = BIT_DPDT_WLBT_SEL set, [22] clear, applied post-coex;
    2. OFDM 1SS TX must ride ONE path (kernel txpath_1ss semantics): 0x820=0x32 (1ss-B) at 5 GHz / 0x31 at 2.4 GHz. The old 1ss-on-both mapping interacts with the DPDT fix (MCS0 wedges).
      Result: MCS7 goes 0 → ~5000 clean/12 s EVM −48; full ladder MCS0/4/7/54M clean; 40 MHz 5226 @ −53; 80 MHz TX verified against an independent kernel ground (5658 frames).
  • Quirk Small mods to build on android #1 (0x41e8 path-B RX-desense) RESOLVED: it was a downstream artifact of the same front-end mis-config, not TSSI asymmetry (tests/eu_41e8_desense_recheck.sh: poking the ref with RX live = −2 %, noise). The structural skip is retired — path-B OFDM TX power is controllable in TX+RX mode again (full-duplex proof: 24k RX frames + 14.3k clean MCS7 simultaneously, tests/eu_fullduplex_pathb_check.sh).
  • RF reg 0x0 writes silently no-op'd through the direct BB window — routed through the legacy FON port 0x1808/0x4108 per the kernel, in both halrf impls AND the radio-table loader (the vendor tables carry reg-0 entries). Verified via the TXGAPK gain-table readback (all-zero before, kernel-shaped ramps after).
  • Construction-time TX-power state was clobbered by the FW power/coex H2Cs — authoritative TXAGC re-apply now runs after them; PAD_CTRL1[29:28] re-asserted there too.

Kernel channel/runtime obligations ported (systematic source diff vs reference/rtl88x2eu)

  • Spur elimination (14 harmonic channel/BW combos + explicit spur-free default, every switch; FastRetune declines spur hops — hop parity PASS at 20 and 40 MHz)
  • CCK TX shaping filter (per-2.4 GHz-channel, ch14 special set)
  • IGI toggle after every full channel set, both variants — fixes the intermittent post-switch RX deafness / first-cell TX wedge (CU ch6 ground liveness 5/5, EU wedge 0/6 repro)
  • LCK synthesizer re-lock on ≥4-unit thermal drift (fired live in a 22-min max-duty soak: 1.10 M clean MCS7 frames, EVM drift +3 dB)
  • phydm monitor-mode dynamic mechanisms (PhydmRuntimeJaguar3): FA/CCA statistics, unlinked DIG, CCK-PD type4, EDCCA tracking — with one hardware-forced deviation: the DIG floor is 0x1e, not phydm's generic 0x1c (at 0x1c the 8822CU's dense-QAM RX decodes 0 of 65k MCS7 frames; 0x1e passes 65.9k — same class as the kernel's per-IC "For HW setting" exceptions). RX-only sessions tick from a dedicated thread (register I/O on the bulk-IN event thread starves its own event loop).

Verified non-gaps & closures

  • TSSI subsystem NOT needed (kernel runs thermal mode here; 0x1e7c[30]=0 under both drivers); thermal LUT already at parity; TX descriptor byte-identical (48 B).
  • Matched-power TX EVM parity (tests/eu_matched_power_evm.sh, refs bit-identical): MCS7 median −60 under both drivers.
  • Quirk Fix unreadable code in logs #3 (2.4 GHz TX): kernel parity — the module airs undecodable 2.4 GHz under BOTH drivers (in-session verified ground; 51k kernel frames → 0). README bench row corrected; not a devourer gap.
  • Quirks Log libusb error code in rtw_read #2/Remove color code in logger #6 validated (2.4 GHz RX on ch1/6/11 + 11× 5↔2.4 band-hop cycles; RF 0x1a TX_CCK_IND held post-IQK in full and fast paths).

Out of scope / follow-ups

ctest 19/19 across the rebase onto current master; per-chip config subsets build (spot-checked no-Jaguar3).

🤖 Generated with Claude Code

josephnef and others added 2 commits July 12, 2026 09:59
…nel/runtime obligations (#238)

Root causes found by golden end-state diff (kernel via /proc read_reg live vs
devourer via new DEVOURER_BB_DUMP, 0x000..0x4ffc) + subset bisection with a
Jaguar3 DEVOURER_REPLAY_WSEQ port, kernel-TX ground truth on the same unit.

Fixes (all bench-validated, 8812EU->8822CU ch36 + SDR):
- MCS4+/48M+ whole-PPDU TX corruption: route the DPDT antenna switch to WL
  control (REG_LED_CFG 0x4c[24] = BIT_DPDT_WLBT_SEL, [22] clear; halmac
  pinmux family devourer's hand-rolled MAC init skipped) + single-path OFDM
  1SS TX routing (0x820: 1ss-B@5G / 1ss-A@2G, kernel semantics; the old
  1ss-on-AB mapping interacts with the DPDT fix and wedges MCS0). MCS7 goes
  0 -> ~5000 clean/12s EVM -48; full ladder MCS0/4/7/54M clean.
- 0x41e8 path-B RX-desense quirk RESOLVED: it was a downstream artifact of
  the same front-end mis-config, not TSSI asymmetry. Structural skip retired;
  path-B OFDM TX power is controllable in TX+RX mode again (full-duplex
  validated: 24k RX frames + 14.3k clean MCS7 simultaneously).
- RF reg 0x0 writes: direct-window writes silently no-op; route through the
  legacy FON port 0x1808/0x4108 (addr<<20|data) in Halrf8822e, Halrf8822c
  and the radio-table loader (vendor tables carry reg-0 entries).
- Construction-time TX-power state was clobbered by the FW power/coex H2Cs:
  authoritative TXAGC re-apply now runs post-coex (with readback diff log);
  PAD_CTRL1[29:28] re-asserted there too.
- Channel-switch ports from the kernel switch_channel tail: spur elimination
  (14 harmonic channel/BW combos, NBI+CSI mask; FastRetune declines spur
  hops), CCK TX shaping filter (per-2G-channel, ch14 special), IGI toggle on
  BOTH Jaguar3 variants (fixes the intermittent post-switch RX deafness /
  MCS0 first-cell TX wedge; CU ch6 ground liveness now 5/5).
- LCK synthesizer re-lock (AACK+RTK on >=4-unit thermal drift) wired into the
  coex-tick thermal tracker; fired live in a 22-min max-duty soak (1.10M
  clean MCS7 frames, EVM drift +3 dB).

Verified non-gaps: TSSI subsystem (kernel runs THERMAL mode here, 0x1e7c[30]
= 0 both drivers), thermal codeword LUT (end-state identical), TX descriptor
(48B, byte-identical). 2.4 GHz TX airs undecodable frames under BOTH drivers
(kernel-parity, module property) -- README row corrected, quirk documented.

Docs: docs/8822e-quirks.md (the issue deliverable -- every quirk root-caused
or kernel-parity-proven, with reproducers). Diag: replay hook at end of both
bring-ups, DEVOURER_BB_DUMP, coex-tick TXAGC register watch,
DEVOURER_TX_PWR_OFFSET_QDB on txdemo. Tests: 14 new harnesses
(tests/eu_*.sh, cu_2g_ground_liveness.sh, sdr_burst_len.py);
txpwr_offset_regcheck.sh path-B cell inverted to the new semantics.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…D/EDCCA)

The vendor watchdog's RX-side adaptation loop, run every ~2 s like the
kernel's: false-alarm/CCA window statistics (read + reset,
phydm_fa_cnt_statistics_jgr3), unlinked DIG (IGI stepping in 0x1d70 by FA
level, DFS channels pinned to 0x20), CCK packet-detection type4 (CCK-FA
moving average driving the PD/CS ladder in 0x1ac8/0x1acc/0x1ad0), and EDCCA
threshold tracking (0x84c, th_l2h = max(IGI+8, 48) / -8 — matches the
kernel's end state; suppressed while SetCcaMode's EDCCA-disable is active).

The DIG coverage window floors at 0x1e, not phydm's generic
DIG_MIN_COVERAGE 0x1c: at IGI 0x1c the 8822CU's dense-QAM RX decodes
NOTHING (0 of 65k kernel-injected MCS7 frames; 0x1e passes 65.9k) —
hardware-bisected, value-specific, the same class of per-IC "For HW
setting" floor exception the kernel carries for other ICs.

TX/TX+RX sessions tick from the coex thread; RX-only sessions get a
dedicated housekeeping thread — register I/O cannot run on the bulk-IN
event thread (a sync control transfer there starves its own event loop).

The kernel's remaining watchdog mechanisms self-disable without a link and
are intentionally absent: CFO tracking (returns unless associated), RA and
the beamforming watchdog (no station entries), antenna diversity (2T2R).

Matched-power TX EVM parity verified (tests/eu_matched_power_evm.sh, one
ground session, refs bit-identical at devourer offset -44 == kernel
`txpower fixed 500`): MCS7 median EVM -60 both drivers; MCS0 -64 vs -66.
Default-power ladder unchanged (MCS7 6407 clean / -48, MCS0 5680 / -27).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef josephnef merged commit b5a6df7 into master Jul 12, 2026
15 checks passed
@josephnef josephnef deleted the 238-8822e-quirks-campaign branch July 12, 2026 07:07
josephnef pushed a commit that referenced this pull request Jul 15, 2026
…less under the static DPDT route (#289)

## Summary

The 8822E front-end fix (#268 / b5a6df7) routes the DPDT antenna
transfer switch with a static two-bit write: `REG_LED_CFG 0x4c[24]` set,
`[22]` clear. That made MCS4+ TX work — but it parks the switch in a
TX-favoring position that **disconnects RX path B's antenna on every
8812EU**: chain-B pwdb pins at the noise floor (~10 raw / −99 dBm) no
matter which antenna is fitted.

Nobody saw it because every RX validation to date — including #268's
full-duplex proof and the 0x41e8 desense recheck — counts **total
frames**, and chain A alone carries the stream. The loss is real,
though: a dead chain B forfeits MRC diversity (3–5 dB, more in NLOS
multipath).

This PR replaces the static write with a port of the kernel's actual
mechanism: `_efem_pinmux_config` (rtl8822e_halinit.c, rfe 21–24) →
halmac `pinmux_set_func_8822e` for `RFE_CTRL_3/5/7/8/9/11`, transcribed
entry-for-entry from `halmac_gpio_8822e.c`. GPIO13 becomes `WL_DPDT_SEL`
driven by the RFE engine's RFE_CTRL_9, so the transfer switch follows
TX/RX **in hardware**: PA on TX, both LNAs on RX — kernel parity.

## Evidence

Two-adapter bench (BL-M8812EU2 ↔ 8812EU, ch149, 20 MHz, MCS5 at 6 Mbps
offered unless noted, txagc 30 near-field, per-chain rssi/snr from the
phystatus via a per-chain-reporting RX tool). Same card, back-to-back
bring-ups, only the DPDT route changing:

| DPDT route | RX chain A | RX chain B | TX @ MCS7 (10M offered) |
|---|---|---|---|
| static `0x4c[24]`+`[22]`-clear (current) | −68 dBm / 27 dB | **−99 dBm
/ dead** | 9.9M goodput, clean |
| `0x4c[24]` only (halmac `WL_DPDT_SEL` bit alone) | −68 / 27 | −77 / 19
| **airs nothing** |
| eFEM pinmux (this PR) | −70 / 26 | **−77 / 19** | **9.7M goodput,
clean** |

- The bit-level bisect that found it: with the full merge in place,
skipping only the DPDT block brings chain B back; skipping only the 1SS
TX mapping does not. Within the block, keeping `[22]` untouched revives
chain B (and breaks TX — hence neither bit combination is sufficient;
the pin needs its function claim).
- Read-back before the write shows `0x4c[22]` SET from reset
(`0x0062e282`), i.e. GPIO13 parked as BT_WAKE — which is also why
`[24]`-only TX airs nothing: no one has claimed the pin for the RFE
engine. The missing piece is the pin-function claim (`0x42[1]` on GPIO13
+ `0x43[3]/[4]` fixups), which only the full pinmux walk performs.
- Validated with both link ends running this port **simultaneously**: TX
MCS7 9.7M / MCS5 6.0M at 0.0% loss / MCS0 clean, while RX reports both
chains live (−70/−77 dBm, snr 26/19) — the combination that was
previously impossible.
- 0x41e8 history (quirks doc updated): the register is exonerated by
direct A/B; the "RX desense" it was blamed for was this antenna
disconnection all along, and both prior rechecks were blind to it
because they counted total frames.

## Implementation notes

- `RtlJaguar3Device::efem_pinmux_8822e()`: per-pin priority-list walk
(halmac semantics — disable every claim above the target: `field := ~val
& msk`; enable the target: `field := val & msk`; stop) + the WL/BT
ownership fixups, as data tables. Kernel call order preserved
(RFE_CTRL_3, 5, 7, 8, 9, 11).
- Runs post-coex in `InitWrite` — coex `GPIO_MUXCFG` writes would mask
it; same ordering lesson as the existing RFE pad block. The
`PAD_CTRL1[29:28]` post-coex re-assert is kept.
- The old routes remain selectable for board A/B:
`DEVOURER_DPDT_MODE=legacy|bit24|skip` (default `efem`).
- `docs/8822e-quirks.md` updated: the pin-mux section now describes the
hardware-controlled route and its A/B matrix, and adds the honest moral
— per-chain RSSI is the only trustworthy RX-health metric on a 2T2R
part.

## Suggested follow-up (not in this PR)

`GetActiveRxPaths`/per-chain health could gate CI-style validation so a
single-chain regression can't hide behind total-frame counts again.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant